<!DOCTYPE html>
<html>
	<head>
		<title>One Html Page Challenge</title>
		<style>
			body {
				margin: 0;
				padding: 0;

				background-color: #222428;
				color: #ff0015;

				font-family: 'Comic Sans MS';
				font-size: 16px;
			}

			.page-content {
				position: absolute;

				width: 100%;
				height: 100%;
			}

			.page-content-center {
				position: absolute;

				width: 648px;

				left: 50%;
    			transform: translate(-50%, 0);
			}

			.game-title {
				padding: 8px;

				font-size: 20px;
				text-align: center;
			}

			.game-note {
				padding: 8px;
				color : #ff6700;
				text-align: center;
			}

			.game-text {
				padding: 8px;
				text-align: center;
			}

			.game-conf {
				padding: 8px;
			}

			.game-conf label {
				display: inline-block;
				width: 250px;
			}

			.game-conf select {
				display: inline-block;
				width: 50px;
			}

			.game-conf button {
				display: inline-block;
				margin-left: 48px;
			}

			.game-content {
				position: relative;
				width: 648px;
				height: 336px;
			}

			canvas {
				position: absolute;
			}
		</style>
	</head>
	<body>
		<section class="page-content">
			<section class="page-content-center">
				<section class="game-title">Pink vs Unknowns</section>
				<section class="game-note">
					Note: Pink vs Unknowns is a simulation where two sides have opposite contradictory missions. You don't play this game but you can change several key params at the bottom of the page. Could you achieve equilibria for those sides or one will always dominate over another?
				</section>
				<section class="game-text">
					Pink is a scientist whose mission is to teleport Unknowns back to their natural habitat. Unknowns are  self-replicating organisms whose mission is to expand the world. The scientist and organisms look for energy and collect it but they use it differently: Pink for healing and filling the teleport, Unknowns for self-replication. When Pink collides with an unknown three situations are feasible: if the teleport is filled with energy Pink teleports an unknown; Pink loses one life however receives invulnerability mode for a while; if Pink is in invulnerability mode he safety passes by an unknown.
				</section>
				<section class="game-content">
					<canvas id="canvas-layer-1" width="648" height="336"></canvas>
					<canvas id="canvas-layer-2" width="648" height="336"></canvas>
				</section>
				<section class="game-conf">
					<div>
						<label>Pink speed:</label>
						<select id="pinkSpeed">
							<option>40</option>
							<option>45</option>
							<option>50</option>
							<option>55</option>
							<option>60</option>
						</select>
					</div>
					<div>
						<label>Pink lives:</label>
						<select id="pinkLives">
							<option>1</option>
							<option>3</option>
							<option>5</option>
							<option>8</option>
							<option>10</option>
						</select>
					</div>
					<div>
						<label>Pink invulnerability (sec):</label>
						<select id="pinkInvulnerability">
							<option>1</option>
							<option>3</option>
							<option>5</option>
							<option>10</option>
							<option>15</option>
						</select>
						<button onclick="restartGame()">Restart game</button>
					</div>
					<div>
						<label>Energy capacity (sec):</label>
						<select id="energyDuration">
							<option>5</option>
							<option>10</option>
							<option>15</option>
							<option>20</option>
							<option>25</option>
						</select>
					</div>
					<div>
						<label>Energy regenerate time (sec):</label>
						<select id="energyRegenerate">
							<option>1</option>
							<option>2</option>
							<option>5</option>
							<option>10</option>
							<option>15</option>
						</select>
					</div>
				</section>
			</section>
		</section>

		<script>

			// config
			const userConfig = {
				regenerate: 5, //sec
				duration: 10,
				lives: 3,
				speed: 45,
				invulnerability: 3
			}

			const urlParams = new URLSearchParams(window.location.search);
			if (urlParams.has('pinkSpeed')) {
				userConfig.speed = urlParams.get('pinkSpeed');
			}
			if (urlParams.has('pinkLives')) {
				userConfig.lives = urlParams.get('pinkLives');
			}
			if (urlParams.has('pinkInvulnerability')) {
				userConfig.invulnerability = urlParams.get('pinkInvulnerability');
			}
			if (urlParams.has('energyDuration')) {
				userConfig.duration = urlParams.get('energyDuration');
			}
			if (urlParams.has('energyRegenerate')) {
				userConfig.regenerate = urlParams.get('energyRegenerate');
			}
			

			const topbar = {
				pinkHeartCol: 3,
				pinkEnergyCol: 6,
				unknownsHeartCol: 22
			}

			const map = {
				landscape: [
					[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 14, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3],
					[4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4],
					[4, 0, 1, 3, 0, 1, 3, 0, 1, 3, 0, 1, 3, 0, 4, 4, 0, 1, 3, 0, 1, 3, 0, 1, 3, 0, 4],
					[4, 0, 6, 7, 0, 4, 4, 0, 6, 7, 0, 6, 7, 0, 6, 7, 0, 4, 4, 0, 4, 4, 0, 4, 4, 0, 4],
					[4, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 0, 6, 7, 0, 6, 7, 0, 4],
					[4, 0, 1, 3, 0, 4, 4, 0, 1, 3, 0, 1, 3, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4],
					[4, 0, 6, 7, 0, 6, 7, 0, 6, 7, 0, 4, 4, 0, 4, 4, 0, 1, 3, 0, 1, 3, 0, 1, 3, 0, 4],
					[4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 0, 6, 7, 0, 6, 7, 0, 4, 4, 0, 6, 7, 0, 4],
					[4, 0, 1, 3, 0, 1, 3, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 4],
					[4, 0, 4, 4, 0, 4, 4, 0, 4, 4, 0, 1, 3, 0, 1, 3, 0, 1, 3, 0, 4, 4, 0, 1, 3, 0, 4],
					[4, 0, 6, 7, 0, 6, 7, 0, 6, 7, 0, 4, 4, 0, 6, 7, 0, 6, 7, 0, 6, 7, 0, 6, 7, 0, 4],
					[4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4],
					[6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 12, 12, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 7],
				],
			};

			let energyCells = [];

			const directionEnum = {
				up: 'up',
				right: 'right',
				down: 'down',
				left: 'left',
				stop: 'stop'
			}
			
			const frame = {
				up: 0,
				right: 2,
				down: 4,
				left: 6,
				stop: 4,
				padding: 1
			}

			const gameConfig = {
				cellSize: 24,
				characterSize: 36,
				width: 27 * 24,
      			height: 14 * 24,
				offsetY: 24,
				topBarOffsetY: 6,
			};

			const defaultCharacterConfig = {
				x: 0,
				y: 0,
				direction: directionEnum.down,
				frame: 4,
				speed: 45,
				frameRate: 300, //ms
				invulnerability: 0,
				lives: 1,
				energy: 0
			};

			//end config	

			// game objects
			// {
			// 	width,
			// 	height
			// 	state: {
			// 		direction
			// 		frame
			// 		speed
			// 		frameRate //ms
			// 		animationTimeoutId
			// 		x
			// 		y
			// 		toX
			// 		toY
			// 		bfsTarget
			//		lives
			//		energy
			//		invulnerability
			//		isInvulnerable
			// 	},
			// }
			let pink;
			let unknownBlue;
			let unknownGreen;
			let unknownGray;
			let unknownRed;

			let energy = {
				image: null,
				regenerate: userConfig.regenerate * 1000, //ms
				duration: userConfig.duration * 1000, // ms,
				regenerateTimeoutId: null,
				map: new Map()
			};

			let shield = {
				image: null
			}

			const headUnknownsList = {
				val: 'headUnknown',
				length: 0
			};
			const tailUnknownsList = {
				tail: 'tailUnknown'
			};
			headUnknownsList.next = tailUnknownsList;


			const headPinkList = {
				val: 'headPink',
				length: 0
			};
			const tailPinkList = {
				tail: 'tailPink'
			};
			headPinkList.next = tailPinkList;
			// end game objects

			// main scene
			const backgroundCanvas = document.getElementById('canvas-layer-1');
			const backgroundContext = backgroundCanvas.getContext('2d');

			const canvas = document.getElementById('canvas-layer-2');
			const context = canvas.getContext('2d');

			context.font = '20px Comic Sans MS';
			context.fillStyle = "white";
			// end main scene

			//update

			let pastTime = 0;

			function update(time) {
				const delta = time - pastTime;
				pastTime = time;

				controll(headUnknownsList, updateRandomMovementStateAndMove);

				let pinkNode;
				let pinky; 
				if (headPinkList.length > 0) {
					pinkNode = headPinkList.next;
					pinky = pinkNode.val;

					if (pinky.state.energy > 0 && headUnknownsList.length > 0) {
						if (!pinky.state.target) {
							pinky.state.target = headUnknownsList.next.val;		
						}
						controll(headPinkList, updateBfsMovementStateAndMove);
					} else {
						controll(headPinkList, updateRandomMovementStateAndMove);
					}
				}

				let pinkCellKey;
				if (pinky){
					// pink collects energy
					pinkCellKey = getKey(getCoordinates(pinky));
					if (energy.map.has(pinkCellKey)) {
						energy.map.delete(pinkCellKey);

						pinky.state.energy += energy.duration / 1000;
						pinky.state.lives++;

						setTimeout(()=> {
							resetDecreaseEnergy(pinky);
						}, 1000)
					}
				}


				let node = headUnknownsList;
				while (node.next && node.next.next) {
					node = node.next;

					const unknown = node.val;
					const unknownCellKey = getKey(getCoordinates(unknown));

					// pink and unknow are in the same cell
					if (pinkCellKey === unknownCellKey) {
						if (pinky.state.energy > 0) {
							// clean up target if catched
							if (pinky.state.target === node.val) {
								pinky.state.target = null;
							}

							// remove unknown
							node.val = node.next.val;
    						node.next = node.next.next;

							headUnknownsList.length--;
						} else if (!pinky.state.isInvulnerable) {
							pinky.state.lives--;

							if (pinky.state.lives === 0) {
								// remove pink
								pinkNode.val = pinkNode.next.val;
								pinkNode.next = pinkNode.next.next;

								headPinkList.length--;
							} else {
								pinky.state.isInvulnerable = true;

								setTimeout(()=> {
									pinky.state.isInvulnerable = false;
								}, pinky.state.invulnerability);
							}

						}
					} else {
						// unknown collects energy
						if (energy.map.has(unknownCellKey)) {
							energy.map.delete(unknownCellKey);

							if (headUnknownsList.length < 100) {
								const newUnknown = createCharacter({
									x: unknown.state.x,
									y: unknown.state.y,
									speed: 20 + Math.floor(Math.random() * 15),
									image: unknown.image
								});

								newUnknown.state.toX = unknown.state.toX;
								newUnknown.state.toY = unknown.state.toY;
								newUnknown.state.direction = unknown.state.direction;
								newUnknown.state.frame = unknown.state.frame;
								
								insertElementToList(newUnknown, headUnknownsList);
							}
						}
					}
				}

				
				context.clearRect(0, 0, gameConfig.width, gameConfig.height);

				context.fillText(pinky ? getNumericTextPipe(pinky.state.lives) : 0, (topbar.pinkHeartCol + 1) * gameConfig.cellSize, gameConfig.cellSize);
				context.fillText(pinky ? getNumericTextPipe(pinky.state.energy) : 0, (topbar.pinkEnergyCol + 1) * gameConfig.cellSize, gameConfig.cellSize);
				context.fillText(getNumericTextPipe(headUnknownsList.length), (topbar.unknownsHeartCol + 1) * gameConfig.cellSize, gameConfig.cellSize);

				if (pinky && pinky.state.isInvulnerable) {
					drawShield();
				}

				if (headUnknownsList.length === 0 ) {
					context.fillText('Pink enjoys the world!', (topbar.pinkEnergyCol + 5) * gameConfig.cellSize, gameConfig.cellSize);
				}
				
				if (headPinkList.length === 0 ) {
					context.fillText('Unknowns enjoy the world!', (topbar.pinkEnergyCol + 3.5) * gameConfig.cellSize, gameConfig.cellSize);
				}

				energy.map.forEach( (cell) => {
					drawEnergy({
						x: cell[1] * gameConfig.cellSize,
						y: gameConfig.offsetY + cell[0] * gameConfig.cellSize
					});
				});

				node = headUnknownsList;
				while (node.next && node.next.next) {
					node = node.next;
					drawCharacter(node.val, delta);
				}

				node = headPinkList;
				while (node.next && node.next.next) {
					node = node.next;
					drawCharacter(node.val, delta);
				}

				window.requestAnimationFrame(update);
			}

			function controll(node, callback) {
				while (node.next && node.next.next) {
					node = node.next;

					character = node.val;
					const state = character.state;
					switch (state.direction) {
						case directionEnum.up: {
							if (state.toY >= state.y) {
								state.y = state.toY;
								callback(character);
							}
							break;
						}
						case directionEnum.right: {
							if (state.toX <= state.x) {		
								state.x = state.toX;
								callback(character);
							}
							break;
						}
						case directionEnum.down: {
							if (state.toY <= state.y) {
								state.y = state.toY;
								callback(character);
							}
							break;
						}
						case directionEnum.left: {
							if (state.toX >= state.x) {
								state.x = state.toX;
								callback(character);
							}
							break;
						}
						case directionEnum.stop: {
							callback(character);
							break;
						}
					}
				};
			}

			function updateRandomMovementStateAndMove(character) {
				const movementDirection = getRandomMovementDirection(character);
    			updateMovementState(character, movementDirection);
			}

			function getRandomMovementDirection(character) {
				const landscape = map.landscape;

				const n = landscape.length - 1;
				const m = landscape[0].length - 1;

				const direction = character.state.direction;

				const path = [];
				path.push(getCoordinates(character));
				const [i, j] = path[0];

				const fork = [];

				// top
				if (direction !== directionEnum.down && i !== 0 && landscape[i - 1][j] === 0) {
					fork.push([i - 1, j]);
				}

				// right
				if (direction !== directionEnum.left && j !== m && landscape[i][j + 1] === 0) {
					fork.push([i, j + 1]);
				}

				// bottom
				if (direction !== directionEnum.up && i !== n && landscape[i + 1][j] === 0) {
					fork.push([i + 1, j]);
				}

				// left
				if (direction !== directionEnum.right && j !== 0 && landscape[i][j - 1] === 0) {
					fork.push([i, j - 1]);
				}

				if (fork.length === 1) {
					// 1 way to go futher
					path.push(fork[0]);
				} else if (fork.length > 1) {
					// 1+ way to go futher
					path.push(fork[Math.floor(Math.random() * fork.length)]);
				} else {
					// only move back
					switch (direction) {
						case directionEnum.up: {
							path.push([i + 1, j]);
						break;
						}
						case directionEnum.right: {
							path.push([i, j - 1]);
						break;
						}
						case directionEnum.down: {
							path.push([i - 1, j]);
						break;
						}
						case directionEnum.left: {
							path.push([i, j + 1]);
						break;
						}
					}
				}

				return getDirection(path);
			}

			function updateBfsMovementStateAndMove(character) {
				const movementDirection = getBfsMovementDirection(character);
    			updateMovementState(character, movementDirection);
			}

			function getBfsMovementDirection(character) {
				if (!character.state.target) {
					return directionEnum.stop;
				}

				const path = bfs(map.landscape, getCoordinates(character), getCoordinates(character.state.target));

				return getDirection(path);
			}

			function bfs(board, start, end) {
				const map = new Map();

				const il = board.length - 1;
				const jl = board[0].length - 1;

				const [startI, startJ] = start;

				const queue = [];

				// we move from end to start
				let key = getKey(end);
				queue.push(key);
				map.set(key, {
					coordinates: end,
					parent: null,
				});

				while (queue.length !== 0) {
					key = queue.shift();

					const chainCell = map.get(key);
					const [i, j] = chainCell.coordinates;

					if (i === startI && j === startJ) {
					return getPath(chainCell);
					}

					// top
					if (i !== 0 && board[i - 1][j] === 0) {
					updateQueueAndCells(queue, map, chainCell, [i - 1, j]);
					}

					// right
					if (j !== jl && board[i][j + 1] === 0) {
					updateQueueAndCells(queue, map, chainCell, [i, j + 1]);
					}

					// bottom
					if (i !== il && board[i + 1][j] === 0) {
					updateQueueAndCells(queue, map, chainCell, [i + 1, j]);
					}

					// left
					if (j !== 0 && board[i][j - 1] === 0) {
					updateQueueAndCells(queue, map, chainCell, [i, j - 1]);
					}
				}

				return [];
			}

			function getKey(coordinates) {
				const [i, j] = coordinates;
				return i + '_' + j;
			}

			function getPath(chainCell) {
				const path = [];

				while (chainCell !== null) {
					path.push(chainCell.coordinates);
					chainCell = chainCell.parent;
				}

				return path;
			}

			function updateQueueAndCells(queue, map, parent, coordinates) {
				const key = getKey(coordinates);

				if (!map.has(key)) {
					queue.push(key);
					map.set(key, {
					coordinates,
					parent,
					});
				}
			}

			function updateMovementState(character, direction) {
				const state = character.state;

				state.direction = direction;
				state.frame = frame[direction];

				resetAnimation(character);

				switch (direction) {
					case directionEnum.up: {
						state.toY = state.toY - gameConfig.cellSize;
						break;
					}
					case directionEnum.right: {
						state.toX = state.toX + gameConfig.cellSize;
						break;
					}
					case directionEnum.down: {
						state.toY = state.toY + gameConfig.cellSize;
						break;
					}
					case directionEnum.left: {
						state.toX = state.toX - gameConfig.cellSize;
						break;
					}
				}
			}

			function getCoordinates(character) {
				const i = Math.floor((-gameConfig.offsetY + character.state.y + character.height / 2) / gameConfig.cellSize);
				const j = Math.floor((character.state.x + character.width / 2) / gameConfig.cellSize);

				return [i, j];
			}

			function getDirection(path) {
				if (path[0] && path[1]) {
					const [cI, cJ] = path[0];
					const [nI, nJ] = path[1];

					if (cI - nI === 1) {
						return directionEnum.up;
					} else if (cJ - nJ === -1) {
						return directionEnum.right;
					} else if (cI - nI === -1) {
						return directionEnum.down;
					} else if (cJ - nJ === 1) {
						return directionEnum.left;
					}
				}

				return directionEnum.stop;
			}

			function getCenteredPosition(index, cellSize, objectSize) {
    			return index * cellSize + cellSize / 2 - objectSize / 2;
  			}

			function drawCharacter(character, delta) {
				const state = character.state;
				
				switch (state.direction) {
					case directionEnum.up: {
						state.y -= state.speed * delta / 1000;
						break;
					}
					case directionEnum.right: {
						state.x += state.speed * delta / 1000;
						break;
					}
					case directionEnum.down: {
						state.y += state.speed * delta / 1000;
						break;
					}
					case directionEnum.left: {
						state.x -= state.speed * delta / 1000;
						break;
					}
				}

-				context.drawImage(
					character.image, 
					frame.padding + (frame.padding * 2 + character.width) * state.frame,
					frame.padding, 
					character.width, 
					character.height, 
					state.x, 
					state.y, 
					character.width, 
					character.height
				);
			}

			function drawEnergy(config) {
				context.drawImage(
					energy.image, 
					0,
					0, 
					gameConfig.cellSize, 
					gameConfig.cellSize, 
					config.x, 
					config.y, 
					gameConfig.cellSize, 
					gameConfig.cellSize
				);
			}

			function drawShield() {
				context.drawImage(
					shield.image, 
					0,
					0, 
					gameConfig.cellSize, 
					gameConfig.cellSize, 
					topbar.pinkHeartCol * gameConfig.cellSize, 
					gameConfig.topBarOffsetY, 
					gameConfig.cellSize, 
					gameConfig.cellSize
				);
			}

			function getNumericTextPipe(num){
				if (num < 100){
					return num;
				}

				return '99+';
			}

			function animate(character) {
				const state = character.state;

				// animation with 2 frames
				if (state.frame === frame[state.direction]) {
					state.frame++;
				} else {
					state.frame--;
				}

				state.animationTimeoutId = setTimeout(()=>{
					animate(character);
				}, state.frameRate);
			}

			function resetAnimation(character){
				if (character.state.animationTimeoutId) {
					clearTimeout(character.state.animationTimeoutId);
				}
				animate(character);
			}

			function addEnergy() {
				const cell = energyCells[Math.floor(Math.random() * energyCells.length)];

				energy.map.set(getKey(cell), cell);

				energy.regenerateTimeoutId = setTimeout(() => {
					addEnergy();
				}, energy.regenerate);
			}

			function resetDecreaseEnergy(character) {
				if (character.state.decreaseEnergyTimeoutId) {
					clearTimeout(character.state.decreaseEnergyTimeoutId);
				}

				decreaseEnergy(character);
			}

			function decreaseEnergy(character) {
				const state = character.state;
				if (state.energy > 0) {
					state.decreaseEnergyTimeoutId = setTimeout(()=> {
						state.energy--;

						decreaseEnergy(character);
					}, 1000);
				}
			}
			//end update

			//create
			function createBackground(sprites) {
				console.log('!!! create background sprites', sprites);
				
				const landscape = map.landscape;
				const row = landscape.length;
				const col = landscape[0].length;
				const landscapeImage = sprites[0];

				for (let i = 0; i < row; i++) {
					for (let j = 0; j < col; j++) {
						const type = landscape[i][j];

						if (type === 0) {
							energyCells.push([i, j]);
						} else {
							backgroundContext.drawImage(
								landscapeImage, 
								frame.padding + (frame.padding * 2 + gameConfig.cellSize) * (type - 1),
								frame.padding, 
								gameConfig.cellSize, 
								gameConfig.cellSize,
								j * gameConfig.cellSize, 
								gameConfig.offsetY + i * gameConfig.cellSize, 
								gameConfig.cellSize, 
								gameConfig.cellSize
							);
						}
					}
				}

				const heartPink = sprites[1];

				backgroundContext.drawImage(
					heartPink, 
					0,
					0, 
					gameConfig.cellSize, 
					gameConfig.cellSize,
					topbar.pinkHeartCol * gameConfig.cellSize, 
					gameConfig.topBarOffsetY, 
					gameConfig.cellSize, 
					gameConfig.cellSize
				);

				const heartEnergy = sprites[2];

				backgroundContext.drawImage(
					heartEnergy, 
					0,
					0, 
					gameConfig.cellSize, 
					gameConfig.cellSize,
					topbar.pinkEnergyCol * gameConfig.cellSize, 
					gameConfig.topBarOffsetY, 
					gameConfig.cellSize, 
					gameConfig.cellSize
				);

				const heartUnknowns = sprites[3];

				backgroundContext.drawImage(
					heartUnknowns, 
					0,
					0, 
					gameConfig.cellSize, 
					gameConfig.cellSize,
					topbar.unknownsHeartCol * gameConfig.cellSize, 
					gameConfig.topBarOffsetY, 
					gameConfig.cellSize, 
					gameConfig.cellSize
				);
			}

			function create(sprites) {
				console.log('!!! create sprites', sprites);
				
				pink = createCharacter({
					x: getCenteredPosition(4, gameConfig.cellSize, gameConfig.characterSize),
					y: gameConfig.offsetY + getCenteredPosition(7, gameConfig.cellSize, gameConfig.characterSize),
					speed: userConfig.speed,
					image: sprites[0],
					energy: 0,
					lives: userConfig.lives,
					invulnerability: userConfig.invulnerability * 1000, //ms
					isInvulnerable: false,
				});
				
				unknownBlue = createCharacter({
					x: getCenteredPosition(16, gameConfig.cellSize, gameConfig.characterSize),
					y: gameConfig.offsetY + getCenteredPosition(1, gameConfig.cellSize, gameConfig.characterSize),
					speed: 35,
					image: sprites[1]
				});
				unknownGreen = createCharacter({
					x: getCenteredPosition(19, gameConfig.cellSize, gameConfig.characterSize),
					y: gameConfig.offsetY + getCenteredPosition(1, gameConfig.cellSize, gameConfig.characterSize),
					speed: 30,
					image: sprites[2]
				});
				unknownGray = createCharacter({
					x: getCenteredPosition(22, gameConfig.cellSize, gameConfig.characterSize),
					y: gameConfig.offsetY + getCenteredPosition(1, gameConfig.cellSize, gameConfig.characterSize),
					speed: 25,
					image: sprites[3]
				});
				unknownRed = createCharacter({
					x: getCenteredPosition(25, gameConfig.cellSize, gameConfig.characterSize),
					y: getCenteredPosition(2, gameConfig.cellSize, gameConfig.characterSize),
					speed: 20,
					image: sprites[4],
				});

				energy.image = sprites[5];

				shield.image = sprites[6];
				
				insertElementToList(unknownBlue, headUnknownsList);
				insertElementToList(unknownGreen, headUnknownsList);
				insertElementToList(unknownGray, headUnknownsList);
				insertElementToList(unknownRed, headUnknownsList);

				insertElementToList(pink, headPinkList);

				resetAnimation(pink);
				resetAnimation(unknownBlue);
				resetAnimation(unknownGreen);
				resetAnimation(unknownGray);
				resetAnimation(unknownRed);

				addEnergy();

				decreaseEnergy(pink);

				window.requestAnimationFrame(update);
			}

			function createCharacter(characterConfig) {

				const config = Object.assign(defaultCharacterConfig, characterConfig);

				return {
					width: gameConfig.characterSize,
					height: gameConfig.characterSize,
					state: {
						direction: config.direction,
						frame: config.frame,
						speed: config.speed,
						frameRate: config.frameRate, //ms
						animationTimeoutId: null,
						decreaseEnergyTimeoutId: null,
						x: config.x,
						y: config.y,
						toX: config.x,
						toY: config.y,
						bfsTarget: null,
						energy: config.energy,
						invulnerability: config.invulnerability,
						lives: config.lives
					},
					image: config.image
				}
			}

			function insertElementToList(element, head) {
				const node = {
					val: element,
					next: head.next
				}

				head.next = node;
				head.length++;
			}
			//end create


			// preload
			const backgroundSrc = '';

			const heartPinkSrc = '';

			const energyPinkSrc = '';

			const heartUnknownsSrc = '';

			const energySrc = '';

			const shieldSrc = '';

			const pinkSrc = '';

			const unknownBlueSrc = '';

			const unknownBlueGreenSrc = '';
			
			const unknownBlueGraySrc = '';
			
			const unknownBlueRedSrc = '';

			preloadSprites([backgroundSrc, heartPinkSrc, energyPinkSrc, heartUnknownsSrc]).then((sprites)=>{
				console.log('!!! preload background sprites:', sprites);
				createBackground(sprites);
			});

			preloadSprites([pinkSrc, unknownBlueSrc, unknownBlueGreenSrc, unknownBlueGraySrc, unknownBlueRedSrc, energySrc, shieldSrc]).then((sprites)=>{
				console.log('!!! preload sprites:', sprites);
				create(sprites);
			});

			function preloadSprites(sources) {
				return Promise.all(sources.map(src => {
					return new Promise((resolve, reject) => {
						const image = new Image();
						image.src = src;

						function innerResolve() {
							resolve(image);
						}

						image.onload = innerResolve
						image.onerror = reject
					})
				}))
			}
			// end preload

			selectElement('pinkSpeed', userConfig.speed);
			selectElement('pinkLives', userConfig.lives);
			selectElement('pinkInvulnerability', userConfig.invulnerability);
			selectElement('energyDuration', userConfig.duration);
			selectElement('energyRegenerate', userConfig.regenerate);

			function selectElement(id, valueToSelect) {    
				let element = document.getElementById(id);
				element.value = valueToSelect;
			}

			function restartGame() {
				const url = window.location.href.split('?')[0];
				window.location.href = url
					+ '?pinkSpeed='+document.getElementById('pinkSpeed').value
					+ '&pinkLives='+document.getElementById('pinkLives').value
					+ '&pinkInvulnerability='+document.getElementById('pinkInvulnerability').value
					+ '&energyDuration='+document.getElementById('energyDuration').value
					+ '&energyRegenerate='+document.getElementById('energyRegenerate').value
			}
			// end user's input 
		</script>
	</body>
</html>